package com.yc.framework.web.service;

import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import com.yc.common.constant.PasswordConstants;
import com.yc.common.core.domain.model.EditPassBody;
import com.yc.common.enums.PasswordErrorType;
import com.yc.common.exceptions.ServiceException;
import com.yc.common.core.domain.entity.SysUser;
import com.yc.common.core.redis.RedisCache;
import com.yc.common.exceptions.captcha.CaptchaException;
import com.yc.common.exceptions.captcha.CaptchaExpireException;
import com.yc.common.exceptions.user.PasswordException;
import com.yc.common.exceptions.user.UserPasswordNotMatchException;
import com.yc.common.exceptions.user.UserPasswordRetryLimitExceedException;
import com.yc.common.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 密码服务
 */
@Component
public class SysPasswordService {

    // 默认最大密码重试次数
    private static final int DEFAULT_MAX_RETRY_COUNT = PasswordConstants.DEFAULT_MAX_RETRY_COUNT;

    // 默认锁定时间（分钟）
    private static final int DEFAULT_LOCK_TIME = PasswordConstants.DEFAULT_LOCK_TIME;

    // 密码强度正则（6-15位，必须包含数字和字母，必须包含特殊字符(@、#、_、~)）
    private static final String PASSWORD_PATTERN = PasswordConstants.PASSWORD_PATTERN;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 获取密码错误次数缓存键
     */
    private String getCacheKey(String identification) {
        return PasswordConstants.PWD_ERR_CNT_KEY + identification;
    }

    /**
     * 验证密码 （登录）
     */
    public void validate(SysUser user, String password) {
        String phoneNumber = user.getPhonenumber(); // 使用手机号作为用户标识

        // 获取密码错误次数
        Integer retryCount = redisCache.getCacheObject(getCacheKey(phoneNumber));
        retryCount = retryCount == null ? 0 : retryCount;

        // 检查是否超过最大重试次数
        if (retryCount >= DEFAULT_MAX_RETRY_COUNT) {
            throw new UserPasswordRetryLimitExceedException(DEFAULT_MAX_RETRY_COUNT, DEFAULT_LOCK_TIME);
        }

        // 验证密码
        if (!matches(user, password)) {
            // 密码错误，增加错误计数
            retryCount++;

            // 设置错误次数缓存
            redisCache.setCacheObject(getCacheKey(phoneNumber), retryCount, DEFAULT_LOCK_TIME, TimeUnit.MINUTES);

            // 计算剩余尝试次数
            int remainingAttempts = DEFAULT_MAX_RETRY_COUNT - retryCount;

            // 如果达到最大错误次数，抛出限制异常
            if (retryCount >= DEFAULT_MAX_RETRY_COUNT) {
                throw new UserPasswordRetryLimitExceedException(DEFAULT_MAX_RETRY_COUNT, DEFAULT_LOCK_TIME);
            } else {
                // 抛出密码不匹配异常，包含剩余尝试次数提示
                throw new UserPasswordNotMatchException("用户密码不正确，您还有" + remainingAttempts + "次尝试机会");
            }


        }

        // 密码验证成功，清除错误记录
        clearLoginRecordCache(phoneNumber);
    }

    /**
     * 校验当前用户输入密码是否正确（忘记密码）
     */
    public void validatePassword(EditPassBody editPassBody, String oldPassword) {
        String email = editPassBody.getEmail();
        String password = editPassBody.getPassword();
        String confirmPassword = editPassBody.getConfirmPassword();
        String code = editPassBody.getCode();
        String captchaId = editPassBody.getCaptchaId();

        // 1. 非空校验
        if (StringUtil.isEmpty(password)) {
            throw new PasswordException(PasswordErrorType.OTHER, "密码不能为空");
        }
        if (StringUtil.isEmpty(confirmPassword)) {
            throw new PasswordException(PasswordErrorType.OTHER, "确认密码不能为空");
        }
        if (StringUtil.isEmpty(code)) {
            throw new PasswordException(PasswordErrorType.OTHER, "验证码不能为空");
        }

        // 2. 验证码校验（防止自动化攻击）
        String cacheCode = redisCache.getCacheObject("captcha:" + captchaId);
        if (cacheCode == null) {
            throw new CaptchaExpireException("验证码已失效");
        }
        if (!cacheCode.equalsIgnoreCase(code)) {
            throw new CaptchaException("验证码错误");
        }
        redisCache.deleteObject("captcha:" + captchaId);

        // 3. 密码和确认密码一致性校验
        if (!password.equals(confirmPassword)) {
            throw new PasswordException(PasswordErrorType.OTHER, "密码与确认密码不一致，请重试");
        }

        // 4. 检查密码修改频率（防止暴力破解）
        String rateLimitKey = "pwd:reset:rate:" + email;
        Integer attempts = redisCache.getCacheObject(rateLimitKey);
        attempts = (attempts == null) ? 0 : attempts;

        if (attempts >= 5) { // 限制5次尝试
            throw new PasswordException(PasswordErrorType.ATTEMPTS_EXCEEDED, "操作过于频繁，请稍后再试");
        }

        // 5. 加密新密码
        String newPassword = passwordEncoder.encode(password);

        // 6. 验证新旧密码不能相同
        if (passwordEncoder.matches(password, oldPassword)) {
            // 使用 matches 比较加密后的密码
            throw new PasswordException(PasswordErrorType.HISTORY_REUSE);
        }

        // 7. 验证新密码强度
        validatePasswordStrength(password);

        // 8. 增加尝试次数（5分钟内有效）
        redisCache.setCacheObject(rateLimitKey, attempts + 1, (long) DEFAULT_LOCK_TIME, TimeUnit.MINUTES);

        // 9. 密码验证成功，清除错误记录和验证码
        clearLoginRecordCache(email);
        redisCache.deleteObject(captchaId);
    }

    /**
     * 密码匹配验证
     */
    public boolean matches(SysUser user, String rawPassword) {
        return passwordEncoder.matches(rawPassword, user.getPassword());
    }

    /**
     * 清除登录错误记录
     */
    public void clearLoginRecordCache(String phoneNumber) {
        if (redisCache.hasKey(getCacheKey(phoneNumber))) {
            redisCache.deleteObject(getCacheKey(phoneNumber));
        }
    }

    /**
     * 加密密码
     */
    public String encryptPassword(String password) {
        return passwordEncoder.encode(password);
    }

    /**
     * 验证密码强度
     */
    public void validatePasswordStrength(String password) {
        if (password == null || password.length() < 6 || password.length() > 15) {
            throw new ServiceException("密码长度必须在6-15位之间");
        }

        if (!Pattern.matches(PASSWORD_PATTERN, password)) {
            throw new ServiceException("密码必须包含字母、数字和特殊字符(@、#、_、~)，并只能包含这些字符");
        }
    }

}